<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js - WebGPU - Materials</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<link type="text/css" rel="stylesheet" href="main.css">
	</head>
	<body>

		<div id="info">
			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> WebGPU - Materials
		</div>

		<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>

		<script type="importmap">
			{
				"imports": {
					"three": "../build/three.module.js",
					"three/addons/": "./jsm/",
					"three/nodes": "./jsm/nodes/Nodes.js"
				}
			}
		</script>

		<script type="module">

			import * as THREE from 'three';
			import * as Nodes from 'three/nodes';

			import WebGPU from 'three/addons/capabilities/WebGPU.js';
			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';

			import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js';

			import { ShaderNode, vec3, dot, triplanarTexture, viewportBottomLeft } from 'three/nodes';

			import Stats from 'three/addons/libs/stats.module.js';

			let stats;

			let camera, scene, renderer;

			const objects = [], materials = [];

			init();

			function init() {

				if ( WebGPU.isAvailable() === false ) {

					document.body.appendChild( WebGPU.getErrorMessage() );

					throw new Error( 'No WebGPU support' );

				}

				const container = document.createElement( 'div' );
				document.body.appendChild( container );

				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
				camera.position.set( 0, 200, 800 );

				scene = new THREE.Scene();

				// Grid

				const helper = new THREE.GridHelper( 1000, 40, 0x303030, 0x303030 );
				helper.material.colorNode = new Nodes.AttributeNode( 'color' );
				helper.position.y = - 75;
				scene.add( helper );

				// Materials

				const textureLoader = new THREE.TextureLoader();

				const texture = textureLoader.load( './textures/uv_grid_opengl.jpg' );
				texture.wrapS = THREE.RepeatWrapping;
				texture.wrapT = THREE.RepeatWrapping;

				const opacityTexture = textureLoader.load( './textures/alphaMap.jpg' );
				opacityTexture.wrapS = THREE.RepeatWrapping;
				opacityTexture.wrapT = THREE.RepeatWrapping;

				let material;

				//
				//	BASIC
				//

				// PositionNode.LOCAL
				material = new Nodes.MeshBasicNodeMaterial();
				material.colorNode = new Nodes.PositionNode( Nodes.PositionNode.LOCAL );
				materials.push( material );

				// NormalNode.LOCAL
				material = new Nodes.MeshBasicNodeMaterial();
				material.colorNode = new Nodes.NormalNode( Nodes.NormalNode.LOCAL );
				materials.push( material );

				// NormalNode.WORLD
				material = new Nodes.MeshBasicNodeMaterial();
				material.colorNode = new Nodes.NormalNode( Nodes.NormalNode.WORLD );
				materials.push( material );

				// NormalNode.VIEW
				material = new Nodes.MeshBasicNodeMaterial();
				material.colorNode = new Nodes.NormalNode( Nodes.NormalNode.VIEW );
				materials.push( material );

				// TextureNode
				material = new Nodes.MeshBasicNodeMaterial();
				material.colorNode = new Nodes.TextureNode( texture );
				materials.push( material );

				// Opacity
				material = new Nodes.MeshBasicNodeMaterial();
				material.colorNode = new Nodes.UniformNode( new THREE.Color( 0x0099FF ) );
				material.opacityNode = new Nodes.TextureNode( texture );
				material.transparent = true;
				materials.push( material );

				// AlphaTest
				material = new Nodes.MeshBasicNodeMaterial();
				material.colorNode = new Nodes.TextureNode( texture );
				material.opacityNode = new Nodes.TextureNode( opacityTexture );
				material.alphaTestNode = new Nodes.UniformNode( 0.5 );
				materials.push( material );

				//
				//	ADVANCED
				//

				// Custom ShaderNode ( desaturate filter )

				const desaturateShaderNode = new ShaderNode( ( input ) => {

					return dot( vec3( 0.299, 0.587, 0.114 ), input.color.xyz );

				} );

				material = new Nodes.MeshBasicNodeMaterial();
				material.colorNode = desaturateShaderNode.call( { color: new Nodes.TextureNode( texture ) } );
				materials.push( material );

				// Custom ShaderNode(no inputs) > Approach 2

				const desaturateNoInputsShaderNode = new ShaderNode( () => {

					return dot( vec3( 0.299, 0.587, 0.114 ), Nodes.texture( texture ).xyz );

				} );

				material = new Nodes.MeshBasicNodeMaterial();
				material.colorNode = desaturateNoInputsShaderNode;
				materials.push( material );

				// Custom WGSL ( desaturate filter )

				const desaturateWGSLNode = new Nodes.FunctionNode( `
					fn desaturate( color:vec3<f32> ) -> vec3<f32> {

						let lum = vec3<f32>( 0.299, 0.587, 0.114 );

						return vec3<f32>( dot( lum, color ) );

					}
				` );

				material = new Nodes.MeshBasicNodeMaterial();
				material.colorNode = desaturateWGSLNode.call( { color: new Nodes.TextureNode( texture ) } );
				materials.push( material );

				// Custom WGSL ( get texture from keywords )

				const getWGSLTextureSample = new Nodes.FunctionNode( `
					fn getWGSLTextureSample( tex: texture_2d<f32>, tex_sampler: sampler, uv:vec2<f32> ) -> vec4<f32> {

						return textureSample( tex, tex_sampler, uv ) * vec4<f32>( 0.0, 1.0, 0.0, 1.0 );

					}
				` );

				const textureNode = new Nodes.TextureNode( texture );
				//getWGSLTextureSample.keywords = { tex: textureNode, tex_sampler: sampler( textureNode ) };

				material = new Nodes.MeshBasicNodeMaterial();
				material.colorNode = getWGSLTextureSample.call( { tex: textureNode, tex_sampler: textureNode, uv: new Nodes.UVNode() } );
				materials.push( material );

				// Triplanar Texture Mapping
				material = new Nodes.MeshBasicNodeMaterial();
				material.colorNode = triplanarTexture( new Nodes.TextureNode( texture ) );
				materials.push( material );

				// Screen Projection Texture
				material = new Nodes.MeshBasicNodeMaterial();
				material.colorNode = Nodes.texture( texture, viewportBottomLeft );
				materials.push( material );

				//
				// Geometry
				//

				const geometry = new TeapotGeometry( 50, 18 );

				for ( let i = 0, l = materials.length; i < l; i ++ ) {

					addMesh( geometry, materials[ i ] );

				}

				//

				renderer = new WebGPURenderer();
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.setAnimationLoop( animate );
				container.appendChild( renderer.domElement );

				//

				stats = new Stats();
				container.appendChild( stats.dom );

				//

				window.addEventListener( 'resize', onWindowResize );

			}

			function addMesh( geometry, material ) {

				const mesh = new THREE.Mesh( geometry, material );

				mesh.position.x = ( objects.length % 4 ) * 200 - 400;
				mesh.position.z = Math.floor( objects.length / 4 ) * 200 - 200;

				mesh.rotation.x = Math.random() * 200 - 100;
				mesh.rotation.y = Math.random() * 200 - 100;
				mesh.rotation.z = Math.random() * 200 - 100;

				objects.push( mesh );

				scene.add( mesh );

			}

			function onWindowResize() {

				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				renderer.setSize( window.innerWidth, window.innerHeight );

			}

			//

			function animate() {

				const timer = 0.0001 * Date.now();

				camera.position.x = Math.cos( timer ) * 1000;
				camera.position.z = Math.sin( timer ) * 1000;

				camera.lookAt( scene.position );

				for ( let i = 0, l = objects.length; i < l; i ++ ) {

					const object = objects[ i ];

					object.rotation.x += 0.01;
					object.rotation.y += 0.005;

				}

				renderer.render( scene, camera );

				stats.update();

			}

		</script>

	</body>
</html>
